/**
 * 
 */
package com.demo2do.strawberry.service.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.redis.GenericRedisSupport;
import com.demo2do.core.utils.StringUtils;
import com.demo2do.core.web.resolver.Page;
import com.demo2do.strawberry.Constant;
import com.demo2do.strawberry.dao.StartupDao;
import com.demo2do.strawberry.entity.investor.Investor;
import com.demo2do.strawberry.entity.startup.Startup;
import com.demo2do.strawberry.entity.startup.activity.Discussion;
import com.demo2do.strawberry.entity.startup.activity.Fans;
import com.demo2do.strawberry.entity.startup.activity.Meet;
import com.demo2do.strawberry.entity.startup.activity.Rating;
import com.demo2do.strawberry.entity.startup.category.Region;
import com.demo2do.strawberry.entity.startup.category.Tag;
import com.demo2do.strawberry.entity.startup.fund.FundProgress;
import com.demo2do.strawberry.entity.startup.fund.FundStatus;
import com.demo2do.strawberry.entity.startup.fund.Weight;
import com.demo2do.strawberry.entity.user.profile.Profile;
import com.demo2do.strawberry.service.StartupService;

/**
 * @author Downpour
 */
@Service("startupService")
public class StartupServiceImpl implements StartupService {
	
	@Value("#{redis['redis.rating.latest.size']}")
	private int ratingLatestSize;
		
	@Value("#{redis['redis.fan.latest.size']}")
	private int fanLatestSize;

	@Value("#{redis['redis.discussion.latest.size']}")
	private int discussionLatestSize;
	
	@Value("#{redis['redis.meet.latest.size']}")
	private int meetLatestSize;
	
	@Autowired
	private GenericDaoSupport genericDaoSupport;
	
	@Autowired
	private GenericRedisSupport genericRedisSupport;
	
	@Autowired
	private StartupDao startupDao;
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#list(com.demo2do.strawberry.entity.startup.fund.FundStatus)
	 */
	@SuppressWarnings("unchecked")
	public List<Startup> list(FundStatus fundStatus) {
		return genericDaoSupport.searchForList("FROM Startup startup WHERE startup.enabled = true AND startup.fundStatus = :fundStatus", "fundStatus", fundStatus);
	}

	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#list(com.demo2do.strawberry.entity.startup.fund.FundStatus, boolean)
	 */
	@SuppressWarnings("unchecked")
	public List<Startup> list(FundStatus fundStatus, boolean withCurrentWeight) {
		
		if(withCurrentWeight) {
			return this.list(fundStatus);
		}
		
		// do not include startups with defaultWeight
		Map<String, Object> parameter = new HashMap<String, Object>();
		parameter.put("fundStatus", fundStatus);
		parameter.put("weight", Weight.aliasOf(fundStatus).getWeight());
		
		return genericDaoSupport.searchForList("FROM Startup startup WHERE startup.enabled = true AND startup.fundStatus = :fundStatus AND startup.weight <> :weight ORDER BY startup.weight DESC", parameter);
	
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#list(com.demo2do.core.web.resolver.Page)
	 */
	@SuppressWarnings("unchecked")
	public List<Startup> list(Page page) {
		
		List<Startup> startups = genericDaoSupport.searchForList("FROM Startup startup WHERE startup.enabled = true ORDER BY startup.weight DESC", Collections.EMPTY_MAP, page.getBeginIndex(), page.getMaxResultRecords());
		page.calculate(startups.size(), 0);
		return (startups.size() >= page.getEveryPage()) ? startups.subList(0, page.getEveryPage()) : startups;
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#list(com.demo2do.core.web.resolver.Page, java.lang.String, java.lang.Long, java.lang.Long, java.lang.Long)
	 */
	@SuppressWarnings("unchecked")
	public List<Startup> list(Page page, String name, Long tid, Long rid, Long fsid) {
		
		// FIXME there is no page logic here, please fix me ASAP
		
		if(tid.intValue() == 0 && rid.intValue() == 0 && fsid.intValue() == 0) {
			if(org.apache.commons.lang.StringUtils.isNotEmpty(name)) {
				
				return genericDaoSupport.searchForList("From Startup startup WHERE startup.profile.name like :name", "name", StringUtils.connect("%", name, "%"));
			}
			return genericDaoSupport.searchForList("From Startup startup ORDER BY startup.weight DESC", Collections.EMPTY_MAP);
			
		} else {
			
			List<Long> idList = new ArrayList<Long>();
			idList.add(tid);
			idList.add(rid);
			idList.add(fsid);
			
			List<String> keyList = new ArrayList<String>();
			if(tid != 0) {
				keyList.add(StringUtils.connect(Constant.REDIS_TAGS, ":", genericDaoSupport.load(Tag.class, tid).getName()));
			} else {
				keyList.add("");
			}
			if(rid != 0) {
				keyList.add(StringUtils.connect(Constant.REDIS_REGIONS, ":", genericDaoSupport.load(Region.class, rid).getName()));
			} else {
				keyList.add("");
			}
			keyList.add(StringUtils.connect(Constant.REDIS_STARTUP_IDS, ".", FundStatus.aliasOf(fsid.intValue()).getAlias()));
			
			Set<String> resultIds = new HashSet<String>();
			for(int index = 0; index < 3; index++) {
				if(idList.get(index).intValue() != 0) {
					resultIds = genericRedisSupport.getMembersForSet(keyList.get(index));
					idList.remove(index);
					keyList.remove(index);
					break;
				}
			}
			
			for(int index = 0; index < 2; index++) {
				if(idList.get(index).intValue() != 0) {
					resultIds.retainAll(genericRedisSupport.getMembersForSet(keyList.get(index)));
				}
			}
			
			if(CollectionUtils.isEmpty(resultIds)) {
				return Collections.EMPTY_LIST;
			} else {
				return startupDao.getStartups(resultIds, name);
			}
		}
		
	}

	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#load(java.io.Serializable)
	 */
	public Startup load(Serializable id) {
		return genericDaoSupport.load(Startup.class, id);
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#loadStartupTags()
	 */
	@SuppressWarnings("unchecked")
	public List<Tag> loadStartupTags() {
		return genericDaoSupport.searchForList("From Tag tag ORDER BY tag.id ASC", Collections.EMPTY_MAP);
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#loadStartupRegions()
	 */
	@SuppressWarnings("unchecked")
	public List<Region> loadStartupRegions() {
		return genericDaoSupport.searchForList("From Region region ORDER BY region.id ASC", Collections.EMPTY_MAP);
	}

	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#createRating(com.demo2do.strawberry.entity.startup.activity.Rating, com.demo2do.strawberry.entity.startup.Startup, com.demo2do.strawberry.entity.user.profile.Profile)
	 */
	public void createRating(Rating rating, Startup startup, Profile profile) {
		
		// save raitng
		genericDaoSupport.save(rating.initialize(startup, profile, false));
		
		// add rating to redis
		String key = StringUtils.connect(Constant.REDIS_STARTUPS, ":", startup.getId().toString(), ":", Constant.REDIS_RATING_LATEST);
		genericRedisSupport.addMembersToList(key, rating.getId().toString());
		
		// trime redis list
		genericRedisSupport.trimList(key, 0, (long)(ratingLatestSize - 1));

		// update redis count
		String ratingCountKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_RATING_COUNT;
		genericRedisSupport.increaseCount(ratingCountKey);
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getRatings(com.demo2do.core.web.resolver.Page, com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Rating> getRatings(Page page, Startup startup) {
		
		List<Rating> ratings = genericDaoSupport.searchForList("From Rating rating where rating.startup = :startup AND rating.recommend = false ORDER BY rating.createTime DESC", "startup", startup, page.getBeginIndex(), page.getMaxResultRecords());
		page.calculate(ratings.size(), 0);
		List<Rating> result = (ratings.size() >= page.getEveryPage()) ? (ratings.subList(0, page.getEveryPage())) : ratings;
		
		for(Rating rating : result) {
			List<Investor> investors = genericDaoSupport.searchForList("From Investor investor where investor.member = :member", "member", rating.getProfile().getMember());
			rating.setInvestor(investors.get(0));
		}
		
		return result;
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getLatestRating(com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Rating> getLatestRating(Startup startup) {
		
		String key = StringUtils.connect(Constant.REDIS_STARTUPS, ":", startup.getId().toString(), ":", Constant.REDIS_RATING_LATEST);
		List<String> ratingIds = genericRedisSupport.getMembersForList(key);
		
		List<Rating> latestRatings = null;
		if(CollectionUtils.isNotEmpty(ratingIds)) {
			latestRatings = new ArrayList<Rating>();
			
			for(String ratingId : ratingIds) {
				Rating rating = genericDaoSupport.load(Rating.class, Long.valueOf(ratingId));
				List<Investor> investors = genericDaoSupport.searchForList("FROM Investor investor WHERE investor.member = :member", "member", rating.getProfile().getMember());
				rating.setInvestor(investors.get(0));
				latestRatings.add(rating);
			}
		} else {
			
			List<Rating> ratings = genericDaoSupport.searchForList("FROM Rating rating WHERE rating.startup = :startup AND rating.recommend = false ORDER BY rating.createTime DESC", "startup", startup);
			latestRatings = ratings.size() > this.ratingLatestSize ? ratings.subList(0, ratingLatestSize) : ratings;
			for(Rating latestRating : latestRatings) {
				List<Investor> investors = genericDaoSupport.searchForList("FROM Investor investor WHERE investor.member = :member", "member", latestRating.getProfile().getMember());
				latestRating.setInvestor(investors.get(0));
			}
			
			// add to redis list
			for(Rating rating : latestRatings) {
				genericRedisSupport.addMembersToList(key, rating.getId().toString());
			}
		}
		
		return latestRatings;
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#createDiscussion(com.demo2do.strawberry.entity.user.profile.Profile, com.demo2do.strawberry.entity.startup.Startup, com.demo2do.strawberry.entity.startup.activity.Discussion)
	 */
	public void createDiscussion(Profile profile, Startup startup, Discussion discussion) {

		discussion = discussion.initialize(profile, startup);
		genericDaoSupport.save(discussion);
			
		// add discussion to redis
		String key = StringUtils.connect(Constant.REDIS_STARTUPS, ":", startup.getId().toString(), ":", Constant.REDIS_DISCUSSION_LATEST);
		genericRedisSupport.addMembersToList(key, discussion.getId().toString());
		
		// trime redis list
		genericRedisSupport.trimList(key, 0, discussionLatestSize - 1);

		// update redis count
		String discussionCountKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_DISCUSSION_COUNT;
		genericRedisSupport.increaseCount(discussionCountKey);
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getDiscussions(com.demo2do.core.web.resolver.Page, com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Discussion> getDiscussions(Page page, Startup startup) {
		List<Discussion> discussions = genericDaoSupport.searchForList("FROM Discussion discussion WHERE discussion.startup = :startup ORDER BY discussion.createTime DESC", "startup", startup, page.getBeginIndex(), page.getMaxResultRecords());
		page.calculate(discussions.size(), 0);
		List<Discussion> result = (discussions.size() >= page.getEveryPage()) ? (discussions.subList(0, page.getEveryPage())) : discussions;
		
		for(Discussion discussion : result) {
			List<Investor> investors = genericDaoSupport.searchForList("FROM Investor investor WHERE investor.member = :member", "member", discussion.getProfile().getMember());
			discussion.setInvestor(investors.get(0));
		}
		
		return result;
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getLatestDiscussions(com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Discussion> getLatestDiscussions(Startup startup) {
		
		String key = StringUtils.connect(Constant.REDIS_STARTUPS, ":", startup.getId().toString(), ":", Constant.REDIS_DISCUSSION_LATEST);
		List<String> discussionIds = genericRedisSupport.getMembersForList(key);
		
		List<Discussion> latestDiscussions = null;
		if(CollectionUtils.isNotEmpty(discussionIds)) {
			latestDiscussions = new ArrayList<Discussion>();
			
			for(String discussionId : discussionIds) {
				Discussion discussion = genericDaoSupport.load(Discussion.class, Long.valueOf(discussionId));
				List<Investor> investors = genericDaoSupport.searchForList("FROM Investor investor WHERE investor.member = :member", "member", discussion.getProfile().getMember());
				discussion.setInvestor(investors.get(0));
				latestDiscussions.add(discussion);
			}
		} else {
			
			List<Discussion> discussions = genericDaoSupport.searchForList("FROM Discussion discussion WHERE discussion.startup = :startup ORDER BY discussion.createTime DESC", "startup", startup);
			latestDiscussions = discussions.size() > this.discussionLatestSize ? discussions.subList(0, discussionLatestSize) : discussions;
			for(Discussion latestDiscussion : latestDiscussions) {
				List<Investor> investors = genericDaoSupport.searchForList("FROM Investor investor WHERE investor.member = :member", "memeber", latestDiscussion.getProfile().getMember());
				latestDiscussion.setInvestor(investors.get(0));
			}
			
			// add to redis list
			for(Discussion discussion : latestDiscussions) {
				genericRedisSupport.addMembersToList(key, discussion.getId().toString());
			}
		}
		
		return latestDiscussions;
	}
	
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#createMeet(com.demo2do.strawberry.entity.startup.activity.Meet, com.demo2do.strawberry.entity.startup.Startup, com.demo2do.strawberry.entity.user.profile.Profile)
	 */
	public void createMeet(Meet meet, Startup startup, Profile profile) {
		
		// save meet
		genericDaoSupport.save(meet.initialize(startup, profile));
		
		// add meet to redis
		String key = StringUtils.connect(Constant.REDIS_STARTUPS, ":", startup.getId().toString(), ":", Constant.REDIS_MEET_LATEST);
		genericRedisSupport.addMembersToList(key, String.valueOf(meet.getId().longValue()));
		
		// trim meet redis list
		genericRedisSupport.trimList(key, 0, (long)(meetLatestSize - 1));

		// update redis count
		String meetCountKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_MEET_COUNT;
		genericRedisSupport.increaseCount(meetCountKey);
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getMeets(com.demo2do.core.web.resolver.Page, com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Meet> getMeets(Page page, Startup startup) {
		List<Meet> meets = genericDaoSupport.searchForList("FROM Meet meet WHERE meet.startup = :startup", "startup", startup, page.getBeginIndex(), page.getMaxResultRecords());
		page.calculate(meets.size(), 0);
		return (meets.size() >= page.getEveryPage()) ? meets.subList(0, page.getEveryPage()) : meets;
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getLatestMeets(com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Meet> getLatestMeets(Startup startup) {
		
		String key = StringUtils.connect(Constant.REDIS_STARTUPS, ":", startup.getId().toString(), ":", Constant.REDIS_MEET_LATEST);
		List<String> meetIds = genericRedisSupport.getMembersForList(key);
		
		List<Meet> latestMeets = null;
		if(CollectionUtils.isNotEmpty(meetIds)) {
			latestMeets = new ArrayList<Meet>();
			for(String meetId : meetIds) {
				Meet meet = genericDaoSupport.load(Meet.class, Long.valueOf(meetId));
				List<Investor> investors = genericDaoSupport.searchForList("FROM Investor investor WHERE investor.member = :member", "member", meet.getProfile().getMember());
				meet.setInvestor(investors.get(0));
				latestMeets.add(meet);
			}
		} else {
			
			List<Meet> meets = genericDaoSupport.searchForList("FROM Meet meet WHERE meet.startup = :startup ORDER BY meet.createTime DESC", "startup", startup);
			latestMeets = meets.size() > this.meetLatestSize ? meets.subList(0, meetLatestSize) : meets;
			for(Meet latestMeet : latestMeets) {
				List<Investor> investors = genericDaoSupport.searchForList("FROM Investor investor WHERE investor.member = :member", "member", latestMeet.getProfile().getMember());
				latestMeet.setInvestor(investors.get(0));
			}
			
			// add to redis list
			for(Meet meet : latestMeets) {
				genericRedisSupport.addMembersToList(key, meet.getId().toString());
			}
		}
		
		return latestMeets;
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#createFan(com.demo2do.strawberry.entity.user.profile.Profile, com.demo2do.strawberry.entity.startup.Startup)
	 */
	public boolean createFan(Profile profile, Startup startup) {
	
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("profile", profile);
		params.put("startup", startup);
	
		// return false if already subscribe
		if(!genericDaoSupport.searchForList("FROM Fans fan WHERE fan.profile = :profile AND fan.startup = :startup", params).isEmpty())
			return false;
	
		// save new fan subscribe
		Fans fan = new Fans(profile, startup);
		genericDaoSupport.save(fan);
	
		// insert into redis
		String fanKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_FAN_LATEST;
		genericRedisSupport.addMembersToList(fanKey, fan.getId().toString());
		genericRedisSupport.trimList(fanKey, 0, fanLatestSize - 1);
	
		// update redis count
		String fanCountKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_FAN_COUNT;
		genericRedisSupport.increaseCount(fanCountKey);
	
		return true;
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getFans(com.demo2do.core.web.resolver.Page, com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Fans> getFans(Page page, Startup startup) {
		List<Fans> fans = genericDaoSupport.searchForList("FROM Fans fans WHERE fans.startup = :startup ORDER BY fans.createTime DESC", "startup", startup, page.getBeginIndex(), page.getMaxResultRecords());
		page.calculate(fans.size(), 0);
		return (fans.size() >= page.getEveryPage()) ? (fans.subList(0, page.getEveryPage())) : fans;
		
	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#getLatestFans(com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public List<Fans> getLatestFans(Startup startup) {
		
		String key = StringUtils.connect(Constant.REDIS_STARTUPS, ":", startup.getId().toString(), ":", Constant.REDIS_FAN_LATEST);
		List<String> fanIds = genericRedisSupport.getMembersForList(key);
		
		List<Fans> latestFans = null;
		if(CollectionUtils.isNotEmpty(fanIds)) {
			latestFans = new ArrayList<Fans>();
			
			for(String fanId : fanIds) {
				latestFans.add(genericDaoSupport.load(Fans.class, Long.valueOf(fanId)));
			}
		} else {
			List<Fans> fans = genericDaoSupport.searchForList("FROM Fans fans WHERE fans.startup = :startup ORDER BY fans.createTime DESC", "startup", startup);
			latestFans = fans.size() > this.fanLatestSize ? fans.subList(0, fanLatestSize) : fans;
			
			// add to redis list
			for(Fans fan : fans) {
				genericRedisSupport.addMembersToList(key, fan.getId().toString());
			}
		}
		
		return latestFans;
	}

	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#removeFan(com.demo2do.strawberry.entity.user.profile.Profile, com.demo2do.strawberry.entity.startup.Startup)
	 */
	@SuppressWarnings("unchecked")
	public boolean removeFan(Profile profile, Startup startup) {
	
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("profile", profile);
		params.put("startup", startup);
	
		List<Fans> fans = genericDaoSupport.searchForList("From Fans fan where fan.profile = :profile AND fan.startup = :startup", params);
		// return false if not subscribe
		if(fans.isEmpty())
			return false;
	
		Fans fan = fans.get(0);
	
		// delete fan
		genericDaoSupport.delete(fan);
	
		// delete from redis
		String fanKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_FAN_LATEST;
		genericRedisSupport.removeMemberFromList(fanKey, fan.getId().toString());
		genericRedisSupport.trimList(fanKey, 0, fanLatestSize - 1);
	
		// update redis count
		String fanCountKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_FAN_COUNT;
		genericRedisSupport.decreaseCount(fanCountKey);
	
		return true;
	}


	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#increaseViewCount(com.demo2do.strawberry.entity.startup.Startup)
	 */
	public void increaseViewCount(Startup startup) {

		String viewCountKey = Constant.REDIS_STARTUPS + ":" + startup.getId() + ":" + Constant.REDIS_VIEW_COUNT;
		genericRedisSupport.increaseCount(viewCountKey);

	}
	
	/* (non-Javadoc)
	 * @see com.demo2do.strawberry.service.StartupService#createFundProgress(com.demo2do.strawberry.entity.startup.fund.FundProgress, com.demo2do.strawberry.entity.user.profile.Profile, com.demo2do.strawberry.entity.startup.Startup)
	 */
	public void createFundProgress(FundProgress fundProgress, Profile profile, Startup startup) {
		
		genericDaoSupport.save(fundProgress.initialize(profile, startup));
		
	}

}
